package org.rambadger.command.model;

import org.axonframework.eventhandling.annotation.EventHandler;
import org.axonframework.eventsourcing.annotation.AbstractAnnotatedEntity;
import org.rambadger.command.event.*;

import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import static org.rambadger.command.event.FileDeleted.SubmissionFileDeleted;
import static org.rambadger.command.event.FileUploaded.SubmissionFileUploaded;


/**
 * Member of {@link UserAggregate} that holds the state of the user's submissions.
 */
public class SubmissionEntity extends AbstractAnnotatedEntity implements Serializable {
    private static final long serialVersionUID = 7501453316561567370L;

    private int submissionId;

    //Compound Key {
    private String platformId;
    private String assignmentId;
    //}

    private Map<String, String> submissionFiles;

    private String submissionMessage;

    private SubmissionState state;

    //Review
    private String reviewerId;
    private String reviewMessage;
    private ReviewOutcome reviewOutcome;

    private int submissionPoints;
    private int reviewPoints;

    /**
     * Instantiates a new Submission entity.
     *
     * @param submissionId the submission id
     * @param platformId the platform id
     * @param assignmentId the assignment id
     */
    public SubmissionEntity(int submissionId, String platformId, String assignmentId) {
        this.submissionId = submissionId;
        this.platformId = platformId;
        this.assignmentId = assignmentId;

        this.submissionFiles = new HashMap<>();

        this.submissionMessage = "";

        this.state = SubmissionState.ACTIVE;

        this.reviewerId = null;
        this.reviewMessage = null;
        this.reviewOutcome = null;

        this.submissionPoints = 0;
        this.reviewPoints = 0;
    }

    @EventHandler void on(SubmissionMessageEdited event) {
        if (event.getSubmissionId() == submissionId) {
            submissionMessage = event.getMessage();
        }
    }

    @EventHandler void on(SubmissionFileUploaded event) {
        if (event.getSubmissionId() == submissionId) {
            submissionFiles.put(event.getFilename(), event.getFileId());
        }
    }

    @EventHandler void on(SubmissionFileDeleted event) {
        if (event.getSubmissionId() == submissionId) {
            submissionFiles.remove(event.getFilename());
        }
    }

    @EventHandler void on(SubmissionPublished event) {
        if (event.getSubmissionId() == submissionId) {
            state = SubmissionState.PUBLISHED;
        }
    }

    @EventHandler void on(SubmissionClaimedForReview event) {
        if (event.getSubmissionId() == submissionId) {
            reviewerId = event.getReviewerId();
            state = SubmissionState.CLAIMED;
            submissionMessage = "";
        }
    }

    @EventHandler void on(ReviewMessageEdited event) {
        if (event.getSubmissionId() == submissionId) {
            submissionMessage = event.getNewMessage();
        }
    }

    @EventHandler void on(ReviewFinalized event){
        if (event.getSubmissionId() == submissionId) {
            //NOTE: This event does not apply points to the user
            reviewOutcome = event.getOutcome();
            state = SubmissionState.REVIEWED;
            submissionPoints = event.getSubmissionPointsAwarded();
            reviewPoints = event.getReviewPointsAwarded();
        }
    }

    /**
     * Gets the submission id.
     *
     * @return the submission id
     */
    public int getSubmissionId() {
        return submissionId;
    }

    /**
     * Gets the platform id.
     *
     * @return the platform id
     */
    public String getPlatformId() {
        return platformId;
    }

    /**
     * Gets the assignment id.
     *
     * @return the assignment id
     */
    public String getAssignmentId() {
        return assignmentId;
    }

    /**
     * Gets a map containing the files attached to the submission.
     * <p>
     *     They key is the filename and the value is the database ObjectId
     * </p>
     * @return the submission files
     */
    public Map<String, String> getSubmissionFiles() {
        return submissionFiles;
    }

    /**
     * Gets the message/description attached tot he submission.
     *
     * @return the submission message
     */
    public String getSubmissionMessage() {
        return submissionMessage;
    }

    /**
     * Gets current point in the submission process of the submission.
     *
     * @return the state
     */
    public SubmissionState getState() {
        return state;
    }

    /**
     * Gets userid of the reviewer.
     *
     * @return the reviewer id
     */
    public String getReviewerId() {
        return reviewerId;
    }

    /**
     * Gets message left by the reviewer.
     *
     * @return the review message
     */
    public String getReviewMessage() {
        return reviewMessage;
    }

    /**
     * Gets the number of points awarded for the submission.
     *
     * @return the submission points
     */
    public int getSubmissionPoints() {
        return submissionPoints;
    }

    /**
     * Gets the number of points awarded to the reviewer.
     *
     * @return the review points
     */
    public int getReviewPoints() {
        return reviewPoints;
    }

    /**
     * Checks if the submission is in one of the states passed to the function. If the submission is not in one of those states, an exception is thrown to abort the transaction.
     *
     * @param validStates the states to check for
     * @return whether the submission is in an acceptable state
     * @throws RuntimeException the runtime exception
     */
    public boolean ensureState(SubmissionState... validStates) throws RuntimeException {
        for (int i = 0; i < validStates.length; i++) {
            if (validStates[i] == state) {
                return true;
            }
        }
        //None found
        throw new RuntimeException(String.format("Submission is in state %s. Allowed states: %s", state, Arrays.toString(validStates)));
//        return false;
    }

    /**
     * enum indicating the Submission state.
     */
    public static enum SubmissionState {
        /**
         * The submission has been created and is editable.
         */ACTIVE,
        /**
         * The submission has been turned in for review, leaving it claimable by a reviewer.
         */PUBLISHED,
        /**
         * The submission has been claimed by a reviewer (and removed from the review queue), the claimer can now edit their review before finalizing it.
         */CLAIMED,
        /**
         * The the review has been finalized and everything has been made permanent.
         */REVIEWED
   }

    /**
     * The enum Review outcome.
     */
    public static enum ReviewOutcome {
        /**
         * The submission was given a passing grade, satisfying the completion requirement in a track.
         */PASS, /**
         * The submission was given a failing grade.
         */FAIL
    }
}
